package com.jvm.classfile.model;

import com.jvm.Utils;
import com.jvm.interpreter.Instruction;
import com.jvm.interpreter.instruction.*;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: diy_simple_jvm
 * @description: 指令代码
 * @author: CaoYong
 * @create: 2021-07-08 15:14
 **/
@Data
@AllArgsConstructor
public class Code {
    int maxStack; //最大操作栈大小
    int maxLocals; //最大本地变量表大小

    byte[] bytes; //指令内容
    ExceptionTableItem[] exceptionTable; //异常表
    AttributeInfo[] attributes;

    public Map<Integer, Instruction> getInstructions(CpInfo[] cp) {
        Map<Integer, Instruction> instructionMap = new HashMap<>();
        try (ByteArrayInputStream bis = new ByteArrayInputStream(this.bytes);
             final DataInputStream dis = new DataInputStream(bis)
        ) {
            int pc = 0;
            while (dis.available() > 0) {
                final int opcode = dis.read();
                switch (opcode) {
                    case 0x04:
                        instructionMap.put(pc, new IConst1Inst());
                        pc++;
                        break;
                    case 0x3b:
                        instructionMap.put(pc, new IStore0Inst());
                        pc++;
                        break;
                    case 0x1a:
                        instructionMap.put(pc, new ILoad0Inst());
                        pc++;
                        break;
                    case 0xac:
                        instructionMap.put(pc, new IReturnInst());
                        pc++;
                        break;
                    case 0x03:
                        instructionMap.put(pc, new IConst0Inst());
                        pc++;
                        break;
                    case 0x3c:
                        instructionMap.put(pc, new IStore1Inst());
                        pc++;
                        break;
                    case 0x3d:
                        instructionMap.put(pc, new IStore2Inst());
                        pc++;
                        break;
                    case 0x1b:
                        instructionMap.put(pc, new ILoad1Inst());
                        pc++;
                        break;
                    case 0x1c:
                        instructionMap.put(pc, new ILoad2Inst());
                        pc++;
                        break;
                    case 0x60:
                        instructionMap.put(pc, new IAddInst());
                        pc++;
                        break;
                    case 0x10:
                        instructionMap.put(pc, new BiPushInst(dis.readByte()));
                        pc += 2;
                        break;
                    case 0x84:
                        instructionMap.put(pc, new IIncInst(dis.readUnsignedByte(), dis.readByte()));
                        pc += 3;
                        break;
                    case 0xa7:
                        instructionMap.put(pc, new GotoInst(dis.readShort()));
                        pc += 3;
                        break;
                    case 0xa3:
                        instructionMap.put(pc, new IfIcmpGtInst(dis.readShort()));
                        pc += 3;
                        break;
                    case 0xb2:
                        final int gsfi = dis.readUnsignedShort();
                        instructionMap.put(pc,
                                new GetstaticInst(Utils.getClassNameFromFieldRef(cp, gsfi), Utils.getNameFromFieldRef(cp, gsfi),
                                        Utils.getDescriptorFromFieldRef(cp, gsfi))
                        );
                        pc += 3;
                        break;
                    case 0xb6:
                        final int ivi = dis.readUnsignedShort();
                        instructionMap.put(pc,
                                new InvokeVirtualInst(Utils.getClassNameFromMethodRef(cp, ivi), Utils.getNameFromMethodRef(cp, ivi),
                                        Utils.getDescriptorFromMethodRef(cp, ivi))
                        );

                        pc += 3;
                        break;
                    case 0xb1:
                        instructionMap.put(pc, new ReturnInst());
                        pc++;
                        break;
                    default:
                        throw new IllegalStateException("unknown opcode " + opcode);
                }
            }
        } catch (IOException e) {
            throw new IllegalStateException("method code parse failure");
        }

        return instructionMap;
    }


}
